from tools.send_to_ui_tools import *
from tools.save_image_to_device import *
from tools import common_tools
from db_tools.db_handler import DbHandler
import threading
import gevent
from tools.common_tools import *
# from vision_process.image_process import find_total_board_info, get_image_used, find_total_board_info_6pin
from vision_process.image_process_new import find_total_board_info_4pin, find_total_board_info_6pin, get_image_used
from tools.send_to_ui_tools import *
from tools.config_params import ConfigParams
from tools.ocr_tool import get_datetime_now_str, OCR_get_ng_or_ok_res_new, get_image_used_for_ocr, find_text_by_cnocr, day_add_and_sub
from tools.roi_get import find_ocr_roi

db = DbHandler()
logger = HdLogger()
cp = ConfigParams()

import time
import cv2 as cv
import socket

addr = ("192.168.0.2", 1004)

"""
作业模块,主要负责自动作业的逻辑部分
"""


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)


class ProduceHandler(object):
    """
    生产作业相关的类,这个类主要用来正常的生产作业使用
    """

    def __init__(self, plcClient, camera, deviceStates):
        self.camera = camera
        self.deviceStates = deviceStates
        self.logger = logger
        self.boardIndex = 0
        self.plcClient = plcClient
        self.LastIndexError = 10000  # 上一次的IndexError.
        self.LastUsedAddOne = True  # 默认上一次是按照推测的,设备启动的时候这个功能不能使用
        self.getNumberImageThread = threading.Thread(target=self.web_request_get_number_in_image_color, name="getNumberImageThread")
        self.getNumberImageThread.setDaemon(True)
        self.getNumberImageThread.start()
        data = db.get_working_info()[1]
        self.boardIndex = data.get("boardIndex",0)
        self.indexMax = cp.maxIndex
        # 作业线程开启,作业线程启动.


    def web_request_reset(self):
        """
        界面上点击了复位按钮
        """
        if self.plcClient.connectedStatus != "已断开":
            return self.plcClient.WriteResetCommand()
        return "PLC未连接,复位失败"


    def web_request_reset_alarm(self):
        """
        界面上点击了故障报警复位按钮
        """
        if self.plcClient.connectedStatus != "已断开":
            return self.plcClient.WriteResetAlarmCommand()
        return "PLC未连接,复位失败"

    @staticmethod
    def web_request_login(userInfo):
        """
        前端界面点击登录按钮,登录界面
        :return:
        """
        username = userInfo.get("username", "")
        password = userInfo.get("password", "")

        if username == 'admin' and password == 'hengdingNo.1':
            res = {
                'currentAuthority': 'admin',
                'status': 'ok',
                'type': 'account'
            }
            db.update_login_state(True)
            return res
        userInfo = db.get_user_list_by_username(username)
        if len(userInfo) == 0:
            res = {
                'status': 'error',
                'errorMsg': '你输入的用户名不存在,请重新输入'
            }
            return res
        elif username == "" and password == "":
            res = {
                'status': 'error',
                'errorMsg': '输入的用户名和密码不能是空,请重新输入'
            }
            return res
        elif username != userInfo.get("username", "") or password != userInfo.get("password", ""):
            res = {
                'currentAuthority': 'guest',
                'status': 'error',
                'errorMsg': '你输入的密码错误,请重新输入',
                'type': 'account'
            }
            return res
        else:
            if userInfo.get("role", "") == "普通用户":
                res = {
                    'currentAuthority': "user",
                    'status': "ok",
                    'type': "account"
                }
            else:
                res = {
                    'currentAuthority': "serviceman",
                    'status': "ok",
                    'type': "account"
                }

            # 登录成功,刷新登录时间.
            query = {
                "username": username
            }
            tableName = "userinfo"
            newValues = {
                "$set": {
                    "lastLoginTime": common_tools.get_time_now_string()
                }
            }
            db.update_data(tableName, query, newValues)

            # 更新登录状态
            db.update_login_state(True)
            return res

    def keep_error_msg(self, errorMsg, errorType, boardIndex):
        if errorType == 'mainZmc':
            errorMsg = "主控制器通信异常: " + errorMsg
        else:
            errorMsg = '其他未归类错误'
        msgVal = {
            "val": "errorState",
            "errorInfo": errorMsg
        }
        self.logger.debug("id_{} {}".format(boardIndex, errorMsg))
        send_produce_state_to_ui(msgVal)


    def update_order_info_to_web(self,orderInfoToKeep):
        """
        更新工单信息
        :return:
        """
        db.insert_data(db.orderTable, orderInfoToKeep)
        msg = {
            "val": 1
        }
        send_order_state_to_ui(msg)

    def send_image_to_web(self, imageRes, info):
        """
        发送图片到前端界面
        :param imageRes:识别的结果图
        :return:
        """
        newWidth = 1024
        newHeight = 76
        newImage = cv.resize(imageRes, (newWidth, newHeight), cv.INTER_AREA)
        msgVal = {
            "val": "boardImageState",
            "modalVisible": True,
            "boardImageSrc": image_to_string(newImage),
            "calcInfo": info
        }
        send_produce_state_to_ui(msgVal)

    def increase_board_index(self):
        self.boardIndex += 1
        if self.boardIndex > self.indexMax:
            self.boardIndex = 1
        newData = {
            "boardIndex": self.boardIndex
        }
        db.save_working_info_data(newData)
        # 做板数量加1
        db.update_produce_record(1)

    def web_request_get_number_in_image_color(self):
        lastBoardTime = 0 # 上一块板子拍照时间
        while True:
            if self.deviceStates.workStopFlag:
                self.logger.debug("检测到 wokStopFlag = True, 相机监听作业函数停止!")
                return
            if self.plcClient.connectedStatus != "已连接":
                gevent.sleep(0.1)
                continue
            # 一直监听bit位,是否允许拍照
            ret, val = self.plcClient.GetAllowTakePicture()
            if ret != 0:
                gevent.sleep(0.1)
                continue
            if val == 1:
                gevent.sleep(0.1)
                self.plcClient.SetAllowTakePictureClear()
                if lastBoardTime != 0:
                    intervalToLastBoard = time.time() - lastBoardTime
                else:
                    intervalToLastBoard = 0
                lastBoardTime = time.time()
                self.logger.debug("id_{} OCR监控模块收到拍照信号,相机开始拍照,和上一块拍照间隔:{:.2f}".format(self.boardIndex, intervalToLastBoard))
                self.camera.start_grabbing()
                res, image = self.camera.get_image_color()
                if res != 0:
                    for i in range(3):
                        res, image = self.camera.get_image_color()
                        if res == 0:
                            break
                    else:
                        self.keep_error_msg("id_{} 相机拍照失败三次,请检查电源线和网线!", "mainZmc", self.boardIndex)
                        return
                self.camera.stop_grabbing()
                self.plcClient.TakePicFinish() # 拍照结束
                # image = get_image_used(image)
                calRes = "fail"
                keepDirFlag = "fail"  # 保存图形目录是OK还是Fail

                workTime = get_time_now_string_for_hour()
                orderInfoToKeep = {
                    "boardIndex": 0,
                    "workTime": get_time_now_string_for_hour(),
                    "calcInfo": "识别出错",
                    "realNumber": "识别出错",
                    "calcRes":"无识别结果"
                }
                try:
                    image = get_image_used_for_ocr(image, xSplitStart=0.1, xSplitEnd=0.65)
                    # 获取缩放后的图片
                    res, imageResized = find_ocr_roi(image)
                    if res != 'ok':
                        save_ok_image_color(self.boardIndex, image, "imageError", image)
                    calRes = "ok"
                    # dateDir = ""
                    boardIndexOcr = cp.OCR_LastBoardIndex
                    # 用来存储板子是NG还是OK信号
                    res, info, textInfo = find_text_by_cnocr(imageResized)
                    print("id_{} 字符识别结果:{},{},{}".format(self.boardIndex, res, info, textInfo))
                    self.logger.debug("id_{} 字符识别结果:{},{},{}".format(self.boardIndex, res, info, textInfo))
                    orderInfoToKeep["calcInfo"] = textInfo[0] if len(textInfo) > 0 else "识别失败"
                    # 根据字符识别结果去找NG或者OK信号结果
                    okByFillZero = False
                    if res != "error":
                        if res == 'okByFillZero':
                            okByFillZero = True
                        keepDirFlag = "ok"
                        _, boardIndexOcr = textInfo

                        nowStr = get_datetime_now_str()
                        # dateDir = nowStr[:4] + dateStr
                        [nowStr].extend(day_add_and_sub(nowStr))
                        laterestTwoDate = [nowStr, day_add_and_sub(nowStr)[0]]
                        # 纠正日期,如果日期不是最近两天的时候.并且大于1天以上.就认为是错误的.

                        # try:
                        #     monthError = int(dateDir[-4:-2]) - int(nowStr[-4:-2])
                        #     dateError = int(dateDir[-2:]) - int(nowStr[-2:])
                        #     # 计算的日期,比当前的日期还要超前,理论上是不存在的.
                        #     if (monthError == 0 and dateError > 1) or (abs(monthError) >= 2) or (abs(dateError) > 5):
                        #         dateDir = nowStr
                        #         self.logger.debug("日期计算错误,采用当前日期. monthError: {},dateError: {}".format(monthError, dateError))
                        # except Exception as e:
                        #     self.logger.debug("日期计算出现异常: {},继续往下执行!".format(str(e)))

                        # 是否在最近的三天的时间
                        boardIndexError = boardIndexOcr - cp.OCR_LastBoardIndex
                        # 如果识别的板编号误差很大,并且上一次的板编号误差是1并且上次不是使用的这种推测,这次就推测板编号
                        if abs(boardIndexError) > cp.indexErrorMax and self.LastIndexError == 1 and (not self.LastUsedAddOne) and \
                                0< intervalToLastBoard < cp.intervalThres:
                            boardIndexOcrOld = boardIndexOcr
                            boardIndexOcr = cp.OCR_LastBoardIndex + 1
                            self.LastUsedAddOne = True
                            self.LastIndexError = 1
                            self.logger.debug("id_{} 识别的板号和上一块误差为{},并且上一次识别的IndexError等于1,上次不是按照index+1的模式,这里按照Index+1处理,两次识别间隔:{}".format(
                                self.boardIndex, boardIndexError, intervalToLastBoard))
                            self.logger.debug("id_{} 识别的板编号: {},indexError: {},实际上使用的编号: {}".format(
                                self.boardIndex, boardIndexOcrOld, boardIndexError, boardIndexOcr))
                        else:
                            self.LastUsedAddOne = False
                            self.LastIndexError = boardIndexError
                        # 如果板号差别太大,就认为是识别失败的情况
                        ocrCalcInfo = "id_{},OCR识别结果:{},板号: {},indexError:{}".format(self.boardIndex,
                                                                                                          res, boardIndexOcr, boardIndexError)
                        orderInfoToKeep["realNumber"] = boardIndexOcr
                        self.logger.debug(ocrCalcInfo)
                        query = {
                            "key": "1"
                        }
                        res, configParams = db.get_data_one(db.configParamsTable, query)
                        cp.OCR_LastBoardIndex = configParams.get("ocr_LastBoardIndex", 1)  # OCR使用的识别的板子的编号.
                        cp.OCR_LastDate = configParams.get("ocr_LastDate", "0302")  # 后面使用的日期

                        # 根据板号和日期,去查找对应目录.
                        dateDir = laterestTwoDate[0]
                        trueFileRes, info = OCR_get_ng_or_ok_res_new(dateDir, boardIndexOcr)
                        if ("找不到".format(boardIndexOcr) in info and not okByFillZero):
                            dateDirNow = laterestTwoDate[1]
                            trueFileRes, info = OCR_get_ng_or_ok_res_new(dateDirNow, boardIndexOcr)
                        # 这个才是最终的判断结果
                        # calRes = trueFileRes
                        # self.camZmc.img_result_ok()
                        # if calRes == "ok":
                        #     self.camZmc.img_result_ok()
                        # else:
                        #     self.camZmc.img_result_failed()
                        # print(time.localtime())
                        if ("找不到".format(boardIndexOcr) in info and okByFillZero) or (okByFillZero and abs(boardIndexError) > 100):
                            # 先变更日期找一次，
                            if len(str(boardIndexOcr)) > 4:
                                boardIndexOcr = int(str(boardIndexOcr)[1:])
                            else:
                                boardIndexOcr = int('1' + str(boardIndexOcr))
                            self.logger.debug("变更板号首位后再寻找一次，新板号:{},日期目录:{}".format(boardIndexOcr, dateDir))
                            trueFileRes, info = OCR_get_ng_or_ok_res_new(dateDir, boardIndexOcr)
                            if "人工确认OK的文件({}_b_True)找不到".format(boardIndexOcr) not in info:
                                boardIndexOcr = cp.OCR_LastBoardIndex + 1
                                trueFileRes, info = OCR_get_ng_or_ok_res_new(dateDir, boardIndexOcr)
                                self.logger.debug("上一块识别成功的板号加 1 后再寻找一次，板号:{},日期目录:{}".format(boardIndexOcr, dateDir))
                        calRes = trueFileRes
                        trueFileResInfo = "id_{} 板号:{},日期目录:{},获取OK还是NG结果: {},信息: {}, 时间：{}".format(self.boardIndex, boardIndexOcr, dateDir,
                                                                                              calRes, info, nowStr)
                        print(trueFileResInfo)
                        self.logger.debug(trueFileResInfo)
                    else:
                        if self.LastIndexError == 1 and (not self.LastUsedAddOne) and \
                                0 < intervalToLastBoard < cp.intervalThres:
                            nowStr = get_datetime_now_str()
                            dateDir = nowStr
                            boardIndexOcr = cp.OCR_LastBoardIndex + 1
                            self.logger.debug("id_{} 识别错误,然后满足上一次的板编号间隔为1,时间间隔满足,所以按照板编号加1的方式去处理".format(self.boardIndex))
                            trueFileRes, info = OCR_get_ng_or_ok_res_new(dateDir, boardIndexOcr)
                            calRes = trueFileRes
                            trueFileResInfo = "id_{} 板号:{},日期目录:{},获取OK还是NG结果: {},信息: {}, 时间：{}".format(self.boardIndex, boardIndexOcr, dateDir,
                                                                                                        calRes, info, nowStr)
                            print(trueFileResInfo)
                            self.logger.debug(trueFileResInfo)
                        else:
                            boardIndexOcr = cp.OCR_LastBoardIndex + 1
                            self.LastUsedAddOne = True
                            self.LastIndexError = 1
                            dateDir = get_datetime_now_str()
                            ocrCalcInfo = "id_{} OCR识别结果: 识别失败, 日期,板号: {},日期: {}".format(self.boardIndex, boardIndexOcr, get_datetime_now_str())
                            print(ocrCalcInfo)
                            self.logger.debug(ocrCalcInfo)
                            # 这个才是最终的判断结果
                            calRes = keepDirFlag
                            # self.camZmc.img_result_ok()
                            print(time.localtime())

                    newData = {
                        "ocr_LastBoardIndex": boardIndexOcr,
                        "ocr_LastDate": dateDir
                    }
                    db.save_config_params(newData)
                    cp.reload_config_params()

                except Exception as e:
                    self.plcClient.SetNgResult()
                    errorInfo = "in web_request_get_number_in_image_color() error:{}".format(str(e))
                    print(errorInfo)
                    self.logger.debug(errorInfo)
                finally:
                    # 这里写那个OK还是NG的逻辑
                    # 默认都按照OK来处理,测试的时候先按照OK来处理
                    if calRes == "OK":
                        # 写入OK信号
                        self.plcClient.SetOkResult()
                        save_ok_image_color(self.boardIndex, image, keepDirFlag, imageResized)
                    else:
                        # 写入失败信号
                        self.plcClient.SetNgResult()
                        save_ok_image_color(self.boardIndex, image, keepDirFlag, imageResized)
                    ret, sequenceData = db.get_sequence_data("1")
                    orderInfoToKeep["calcRes"] = "OK" if calRes == "OK" else "NG"
                    orderInfoToKeep["boardIndex"] = self.boardIndex
                    orderInfoToKeep["workTime"] = get_time_now_string_for_hour()
                    try:
                        sequenceDataKey = sequenceData.keys()
                        if "{}".format(self.boardIndex) in sequenceDataKey:
                            del sequenceData["{}".format(self.boardIndex)]
                        # 先取出所有的键值对
                        items = list(sequenceData.items())
                        # 在取出的键值对前面添加一个新的键值对
                        items.insert(0, ("{}".format(self.boardIndex), orderInfoToKeep))
                        my_dict = dict(items)
                        db.save_sequence_data(my_dict)
                    except Exception as e:
                        print(e)
                    # self.update_order_info_to_web(orderInfoToKeep)
                    send_flagData_state_to_ui("flagDataParams")
                    self.increase_board_index()
                    gevent.sleep(0.2)
            else:
                gevent.sleep(0.1)
                continue

    def web_request_get_sequence_data(self):
        """
        根据当前的工单获取对应的螺栓数据显示到界面
        :return:
        """
        ret, data = db.get_sequence_data("1")
        del data["key"]
        return list(data.values())

